package com.zh.webcommon.common.param.secure;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.zh.webcommon.common.support.CopyFieldUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;

import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import static com.zh.mycommon.util.MyCommonUtil.sha256Hex;


/**
 * 对请求数据生成签名
 *
 * @author ZH
 * @date 23:38 2021/10/20
 */
public final class ParamSignUtil {
    public static final int TIMESTAMP_EXPIRE = 60;

    private static final ObjectMapper objectMapper;

    static {
        objectMapper = new ObjectMapper();
        // 设置 Map 的字段按照 key 排序输出
        objectMapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
    }

    /**
     * 验证签名是否一致，且请求是否在有效期60s内
     *
     * @param sign      请求的参数签名
     * @param realSign  真实的签名
     * @param timestamp 时间戳
     * @return bool
     */
    public static boolean validateSign(String sign, String realSign, long timestamp) {
        return sign.hashCode() == realSign.hashCode() && ((System.currentTimeMillis() - timestamp) / 1000 <= TIMESTAMP_EXPIRE);
    }

    /**
     * 为数据对象 生成 Sign值
     *
     * @param reqData   数据
     * @param appSecret app secret
     * @return sign值
     */
    public static String getSign(Object reqData, long timestamp, String appSecret) {
        if (BeanUtils.isSimpleProperty(reqData.getClass())) {
            throw new IllegalArgumentException("不支持的class类型");
        }
        try {
            Map<String, Object> map = reqData instanceof Map ? (Map) reqData : CopyFieldUtil.objToMap(reqData);
            map.put("timestamp", timestamp);
            return SignGeneratorByJsonSort.generateSign(map, appSecret);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }


    static class SignGeneratorByJsonSort {
        /**
         * 生成签名 方式2-使用JSON序列化时指定排序
         * 推荐使用：简单易用
         *
         * @return 生成的签名
         */
        public static String generateSign(Map<String, Object> reqData, String appSecret) {
            List<String> tokenArr = new LinkedList<>();
            tokenArr.add(String.format("%s=%s", "appSecret", appSecret));

            var sortedMap = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
            sortParamMap(reqData, sortedMap);

            sortedMap.forEach((name, value) -> {
                tokenArr.add(String.format("%s=%s", name, value));
            });

            var paramStr = String.join("&", tokenArr);
//            System.out.println(">>> sorted params = " + paramStr);

            return sha256Hex(paramStr);
        }


        /**
         * 将复杂对象json格式化，key排序
         *
         * @param paramMap  map
         * @param sortedMap 排序后的map
         */
        private static void sortParamMap(Map<String, Object> paramMap, TreeMap<String, String> sortedMap) {
            paramMap.forEach((name, value) -> {
                if (BeanUtils.isSimpleValueType(value.getClass())) {
                    sortedMap.put(name, value.toString());
                } else if (value.getClass().isArray()) {
                    List<?> objects = CollectionUtils.arrayToList(value);
                    objects.sort(Comparator.comparing(Object::toString));
                    sortedMap.put(name, objects.toString());
                } else {
                    try {
                        String json = objectMapper.writeValueAsString(value);
                        sortedMap.put(name, json);
                    } catch (Exception ex) {
                        throw new RuntimeException("value json序列化时出错. cause: " + ex.getMessage());
                    }
                }
            });
        }

    }

    /**
     * 生成签名——手工对字段进行排序
     * 不建议使用：比较麻烦
     */
    static class SignGeneratorByManualSort {
        public static String generateSign(Map<String, Object> reqData, String appSecret) {
            TreeMap<String, String> sortedMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER) {{
                put("appSecret", appSecret);
            }};

            //方式一
            traverseMap("", reqData, sortedMap);

            StringBuilder paramStr = new StringBuilder();
            sortedMap.forEach((name, value) -> {
                paramStr.append(name).append("=").append(value).append("&");
            });

//            System.out.println(">>> sorted params = " + paramStr);
            return DigestUtils.sha256Hex(paramStr.toString());
        }

        /**
         * 复杂对象转成map，再循环处理map的值
         *
         * @param prefix    前缀
         * @param innerMap  map
         * @param sortedMap 排序后的map
         */
        private static void traverseMap(String prefix, Map<String, Object> innerMap, TreeMap<String, String> sortedMap) {
            innerMap.forEach((name, value) -> {
                final String ojbPrefix = prefix.isEmpty() ? name : prefix + "." + name;

                if (value instanceof Map map) {
                    traverseMap(ojbPrefix, map, sortedMap);
                } else if (value.getClass().isArray() || value instanceof List) {
                    List<?> objects = value.getClass().isArray() ? CollectionUtils.arrayToList(value) : (List<?>) value;
                    int i = 0;
                    //标记是否是简单数组
                    boolean simpleFlag = false;
                    for (Object item : objects) {
                        if (BeanUtils.isSimpleProperty(item.getClass())) {
                            simpleFlag = true;
                            break;
                        }
                        String arrPrefix = ojbPrefix + "[" + (i++) + "]";
                        try {
                            Map<String, Object> map = item instanceof Map ? (Map) item : CopyFieldUtil.objToMap(item);
                            traverseMap(arrPrefix, map, sortedMap);
                        } catch (Exception ex) {
                            throw new RuntimeException(ex);
                        }
                    }

                    if (simpleFlag) {
                        objects.sort(Comparator.comparing(Object::toString));
                        sortedMap.put(ojbPrefix, objects.toString());
                    }

                } else {
                    sortedMap.put(ojbPrefix, value.toString());
                }
            });
        }

    }

}
